package javanet.communication;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.List;

import javanet.communication.JavaNETCommunicationRequestProto.JavaNETCommunicationRequest;
import javanet.communication.JavaNETCommunicationRequestProto.JavaNETCommunicationRequest.JavaNETCommunicationRequestParameter;
import javanet.communication.JavaNETCommunicationResponseProto.JavaNETCommunicationResponse;
import javanet.context.JavaNETContextHolder;
import javanet.context.JavaNETDefaultFacadeContext;
import javanet.context.JavaNETFacadeContext;
import javanet.exception.JavaNETErrorInformation;
import javanet.listener.JavaNETCommunicationEvent;
import javanet.listener.JavaNETCommunicationListener;
import javanet.util.BeanLocator;
import javanet.util.SerializationUtil;

import javax.ejb.EJB;
import javax.jws.WebMethod;
import javax.jws.WebParam;
import javax.jws.WebService;
import javax.xml.ws.soap.MTOM;

/**
 * @author Markus Holzer
 */
@WebService
@MTOM
public class JavaNETCommunicationProxy {

	@EJB
	private JavaNETCommunicationFactory factory;

	@WebMethod
	public byte[] invokeFacade(@WebParam(name = "data") byte[] data) {

		// Listeners
		List<JavaNETCommunicationListener> listenerList = factory.createRegisteredListeners();
		JavaNETCommunicationEvent event = new JavaNETCommunicationEvent();

		try {

			JavaNETCommunicationRequest request = JavaNETCommunicationRequest
					.parseFrom(data);

			System.out.println("Request: " + getRequestAsString(request));

			// get facade
			Object facade = BeanLocator.lookupByFacadeName(request
					.getFacadeName());
			// get method
			Method method = getMethodToInvoke(facade.getClass(), request);
			event.setMethodToInvoke(method);
			// get parameters
			List<Object> parameterList = getParameterList(method, request);
			// facade context
			JavaNETFacadeContext facadeContext = (JavaNETFacadeContext)parameterList.remove(0);
			event.setFacadeContext(facadeContext);
			JavaNETContextHolder.setFacadeContext(facadeContext);
			event.setParameters(parameterList.toArray());
			// invoke, get return
			fireBeforeInvocationEvent(listenerList, event);
			Object returnValue = method
					.invoke(facade, parameterList.toArray());
			event.setReturnValue(returnValue);
			fireAfterInvocationEvent(listenerList, event);
			// response
			JavaNETCommunicationResponse response = getResponse(method,
					returnValue);
			return response.toByteArray();
		} catch (Exception e) {
			e.printStackTrace();
			event.setException(e);
			fireOnExceptionEvent(listenerList, event);
			String errorCode = "UNDEFINED";
			String errorMessage = e.getMessage();
			if (e.getCause() instanceof JavaNETErrorInformation)
			{
				System.out.println("***** JavaNETErrorInformation");
				JavaNETErrorInformation error = (JavaNETErrorInformation)e.getCause();
				errorCode = error.getErrorCode();
				errorMessage = error.getErrorMessage();
			}
			JavaNETCommunicationResponse response = JavaNETCommunicationResponse
					.newBuilder().setSuccess(false)
					.setErrorCode(errorCode != null ? errorCode : "")
					.setErrorMessage(errorMessage != null ? errorMessage : "").build();
			return response.toByteArray();
		}
		finally
		{
			JavaNETContextHolder.cleanupThread();
		}

	}

	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
	// Util methods
	// ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~


	private JavaNETCommunicationResponse getResponse(Method method,
			Object returnValue) throws Exception {
		System.out.println("returnType = " + method.getReturnType());
		JavaNETCommunicationResponse.Builder builder = JavaNETCommunicationResponse
				.newBuilder();
		builder.setReturnType(method.getReturnType().getName())
				.setSuccess(true);
		System.out.println(method.getReturnType() != void.class);
		if (method.getReturnType() != void.class) {
			System.out.println("serialize returnValue: " + returnValue);
			String returnValueAsXML = SerializationUtil.serializeParameter(returnValue, method.getReturnType());
			builder.setReturnValue(returnValueAsXML);
		}

		return builder.build();
	}

	private List<Object> getParameterList(Method method,
			JavaNETCommunicationRequest request) throws Exception {
		List<Object> parameterList = new ArrayList<Object>();
		// first parameter is facade context
		JavaNETCommunicationRequestParameter param = request
		.getParameter(0);
		String xml = param.getValue();
		JavaNETDefaultFacadeContext context = SerializationUtil.deserializeParameter(xml, JavaNETDefaultFacadeContext.class);
		parameterList.add(context);
		for (int i = 0; i < method.getParameterTypes().length; i++) {
			param = request
					.getParameter(i+1);
			xml = param.getValue();
			Object o = SerializationUtil.deserializeParameter(xml, method.getParameterTypes()[i]);
			parameterList.add(o);
		}
		return parameterList;
	}

	private Method getMethodToInvoke(@SuppressWarnings("rawtypes") Class facadeClass,
			JavaNETCommunicationRequest request) {
		List<Method> foundMethodList = new ArrayList<Method>();
		for (Method m : facadeClass.getMethods()) {
			if (m.getName().equals(request.getFacadeMethod())
					&& m.getParameterTypes().length == request
							.getParameterCount()-1) {
				foundMethodList.add(m);
			}
		}
		if (foundMethodList.size() == 1)
			return foundMethodList.get(0);
		else if (foundMethodList.size() > 1) {
			Method rightMethod = null;
			for (Method m : foundMethodList) {
				rightMethod = m;
				for (int i = 0; i < m.getParameterTypes().length; i++) {
					@SuppressWarnings("rawtypes")
					Class c = m.getParameterTypes()[i];
					if (!c.getName().equals(request.getParameter(i+1).getType()))
						break;
				}
			}
			return rightMethod;
		}
		return null;
	}

	private void fireBeforeInvocationEvent(
			List<JavaNETCommunicationListener> listenerList,
			JavaNETCommunicationEvent event) {
		for (JavaNETCommunicationListener listener : listenerList) {
			listener.beforeFacadeInvocationEvent(event);
		}
	}

	private void fireAfterInvocationEvent(
			List<JavaNETCommunicationListener> listenerList,
			JavaNETCommunicationEvent event) {
		for (JavaNETCommunicationListener listener : listenerList) {
			listener.afterFacadeInvocationEvent(event);
		}
	}

	private void fireOnExceptionEvent(
			List<JavaNETCommunicationListener> listenerList,
			JavaNETCommunicationEvent event) {
		for (JavaNETCommunicationListener listener : listenerList) {
			listener.onException(event);
		}
	}
 


	private static String getRequestAsString(JavaNETCommunicationRequest request) {
		return new StringBuilder().append("FacadeName=")
				.append(request.getFacadeName()).append(", FacadeMethod=")
				.append(request.getFacadeMethod()).append(", ParameterCount=")
				.append(request.getParameterCount()).toString();
	}

}